﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Erebos.GameScreenFiles
{
    class Chunk
    {
        private VertexBuffer _terrainVertexBuffer;
        private IndexBuffer _terrainIndexBuffer;

        private int _terrainWidth;
        public int TerrainWidth
        {
            get { return _terrainWidth; }
        }

        private int _terrainHeight;
        public int TerrainHeight
        {
            get { return _terrainHeight; }
        }

        private float[,] _heightData;
        private float[,] _texData;
        private GraphicsDevice _device;
        private Effect _effect;
        private Texture2D[] _textures = new Texture2D[8];

        private int _x;
        public int X
        {
            get { return _x; }
        }

        private int _y;
        public int Y
        {
            get { return _y; }
        }

        private int _cx;
        public int ChunkX
        {
            get { return _cx; }
        }

        private int _cy;
        public int ChunkY
        {
            get { return _cy; }
        }

        private Rectangle _location;
        public Rectangle Location
        {
            get { return _location; }
        }

        private List<TextureInfos> _textureInfos;

        public Chunk(Texture2D heightMap, Texture2D texMap, int x, int y, int cx, int cy, GraphicsDevice device, Effect effect, List<TextureInfos> texInfos)
        {
            _device = device;
            _effect = effect;
            _x = x;
            _y = y;
            _cx = cx;
            _cy = cy;
            _textureInfos = texInfos;
            _location = new Rectangle(x, y, heightMap.Width, heightMap.Height);
            LoadHeightData(heightMap);
            LoadTextureData(texMap);
            VertexMultitextured[] tmpVertices = SetUpVertices();
            int[] tmpIndices = SetUpIndices();
            CalculateNormals(tmpVertices, tmpIndices);
            _textures[7] = MainGame.Textures["Sand"];
            _textures[6] = MainGame.Textures["Sand"];
            _textures[5] = MainGame.Textures["Sand"];
            _textures[4] = MainGame.Textures["FloorHerringbone"];
            _textures[3] = MainGame.Textures["Sand"];
            _textures[2] = MainGame.Textures["Grass"];
            _textures[1] = MainGame.Textures["Rock"];
            _textures[0] = MainGame.Textures["Snow"];
        }

        public bool IsInsert(Vector2 position)
        {
            return _location.Contains((int)position.X, (int)position.Y);
        }

        public float GetHeightData(Vector3 position)
        {
            if (position.X < 0 || position.Z < 0 || position.X >= TerrainWidth || position.Z >= TerrainHeight)
                return 0;

            return _heightData[(int)position.X, (int)position.Z];
        }

        private void LoadTextureData(Texture2D textureMap)
        {
            Color[] textureMapColors = new Color[_terrainWidth * _terrainHeight];
            textureMap.GetData(textureMapColors);

            _texData = new float[_terrainWidth, _terrainHeight];
            for (int x = 0; x < _terrainWidth; x++)
            {
                for (int y = 0; y < _terrainHeight; y++)
                {
                    _texData[x, y] = textureMapColors[x + y * _terrainWidth].R;
                }
            }
        }

        private void LoadHeightData(Texture2D heightMap)
        {
            _terrainWidth = heightMap.Width;
            _terrainHeight = heightMap.Height;

            Color[] heightMapColors = new Color[_terrainWidth * _terrainHeight];
            heightMap.GetData(heightMapColors);

            _heightData = new float[_terrainWidth, _terrainHeight];
            for (int x = 0; x < _terrainWidth; x++)
            {
                for (int y = 0; y < _terrainHeight; y++)
                {
                    _heightData[x, y] = heightMapColors[x + y * _terrainWidth].R;
                }
            }

        }

        private VertexMultitextured[] SetUpVertices()
        {
            float minHeight = float.MaxValue;
            float maxHeight = float.MinValue;
            for (int x = 0; x < _terrainWidth; x++)
            {
                for (int y = 0; y < _terrainHeight; y++)
                {
                    if (_heightData[x, y] < minHeight)
                        minHeight = _heightData[x, y];
                    if (_heightData[x, y] > maxHeight)
                        maxHeight = _heightData[x, y];
                }
            }

            Random r = new Random();

            VertexMultitextured[] vertices = new VertexMultitextured[_terrainWidth * _terrainHeight];
            for (int x = 0; x < _terrainWidth; x++)
            {
                for (int y = 0; y < _terrainHeight; y++)
                {
                    vertices[x + y * _terrainWidth].Position = new Vector3(x, _heightData[x, y], -y);
                    vertices[x + y * _terrainWidth].TextureCoordinate.X = (float)x / 30.0f;
                    vertices[x + y * _terrainWidth].TextureCoordinate.Y = (float)y / 30.0f;

                    vertices[x + y * _terrainWidth].TexWeights.X = 0;
                    vertices[x + y * _terrainWidth].TexWeights.Y = 0;
                    vertices[x + y * _terrainWidth].TexWeights.Z = 0;
                    vertices[x + y * _terrainWidth].TexWeights.W = 0;
                    vertices[x + y * _terrainWidth].TexWeights2.X = 0;
                    vertices[x + y * _terrainWidth].TexWeights2.Y = 0;
                    vertices[x + y * _terrainWidth].TexWeights2.Z = 0;
                    vertices[x + y * _terrainWidth].TexWeights2.W = 0;

                    foreach (TextureInfos tinfo in _textureInfos)
                    {
                        if (_texData[x, y] <= tinfo.Maximum && _texData[x, y] >= tinfo.Minimum)
                        {
                            switch (tinfo.FileName)
                            {
                                case "sand":
                                    vertices[x + y * _terrainWidth].TexWeights.W = 1;
                                    break;
                                case "grass":
                                    vertices[x + y * _terrainWidth].TexWeights.Z = 1;
                                    break;
                                case "rock":
                                    vertices[x + y * _terrainWidth].TexWeights.Y = 1;
                                    break;
                                case "snow":
                                    vertices[x + y * _terrainWidth].TexWeights.X = 1;
                                    break;
                                case "floorHerringbone":
                                    vertices[x + y * _terrainWidth].TexWeights2.X = 2;
                                    break;
                                default:
                                    vertices[x + y * _terrainWidth].TexWeights.X = 1;
                                    break;
                            }
                            break;
                        }
                    }

                    float total = vertices[x + y * _terrainWidth].TexWeights.X;
                    total += vertices[x + y * _terrainWidth].TexWeights.Y;
                    total += vertices[x + y * _terrainWidth].TexWeights.Z;
                    total += vertices[x + y * _terrainWidth].TexWeights.W;
                    total += vertices[x + y * _terrainWidth].TexWeights2.X;
                    total += vertices[x + y * _terrainWidth].TexWeights2.Y;
                    total += vertices[x + y * _terrainWidth].TexWeights2.Z;
                    total += vertices[x + y * _terrainWidth].TexWeights2.W;

                    vertices[x + y * _terrainWidth].TexWeights.X /= total;
                    vertices[x + y * _terrainWidth].TexWeights.Y /= total;
                    vertices[x + y * _terrainWidth].TexWeights.Z /= total;
                    vertices[x + y * _terrainWidth].TexWeights.W /= total;
                    vertices[x + y * _terrainWidth].TexWeights2.X /= total;
                    vertices[x + y * _terrainWidth].TexWeights2.Y /= total;
                    vertices[x + y * _terrainWidth].TexWeights2.Z /= total;
                    vertices[x + y * _terrainWidth].TexWeights2.W /= total;
                    /*if (minHeight != maxHeight)
                    {
                        if (_heightData[x, y] < minHeight + (maxHeight - minHeight) / 4)
                            vertices[x + y * _terrainWidth].Color = Color.Blue;
                        else if (_heightData[x, y] < minHeight + (maxHeight - minHeight) * 2 / 4)
                            vertices[x + y * _terrainWidth].Color = Color.Green;
                        else if (_heightData[x, y] < minHeight + (maxHeight - minHeight) * 3 / 4)
                            vertices[x + y * _terrainWidth].Color = Color.Brown;
                        else
                            vertices[x + y * _terrainWidth].Color = Color.White;
                    }
                    else
                    {
                        vertices[x + y * _terrainWidth].Color = Color.Green;
                    }*/
                }
            }

            _terrainVertexBuffer = new VertexBuffer(_device, VertexMultitextured.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
            _terrainVertexBuffer.SetData(vertices);

            return vertices;
        }

        private int[] SetUpIndices()
        {
            int[] indices = new int[(_terrainWidth - 1) * (_terrainHeight - 1) * 6];
            int counter = 0;
            for (int y = 0; y < _terrainHeight - 1; y++)
            {
                for (int x = 0; x < _terrainWidth - 1; x++)
                {
                    int lowerLeft = x + y * _terrainWidth;
                    int lowerRight = (x + 1) + y * _terrainWidth;
                    int topLeft = x + (y + 1) * _terrainWidth;
                    int topRight = (x + 1) + (y + 1) * _terrainWidth;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }

            _terrainIndexBuffer = new IndexBuffer(_device, typeof(int), indices.Length, BufferUsage.WriteOnly);
            _terrainIndexBuffer.SetData(indices);

            return indices;
        }

        private void CalculateNormals(VertexMultitextured[] vertices, int[] indices)
        {
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal = new Vector3(0, 0, 0);

            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }

            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();
        }

        public void Draw()
        {
            DrawTerrain();
        }

        private void DrawTerrain()
        {
            _device.DepthStencilState = DepthStencilState.Default;

            RasterizerState rs = new RasterizerState();

            rs.CullMode = CullMode.None;
            rs.FillMode = Camera.FillMode;
            _device.RasterizerState = rs;

            Matrix worldMatrix = Matrix.CreateTranslation(new Vector3(_x, 0, -_y));
            _effect.CurrentTechnique = _effect.Techniques["MultiTextured"];
            _effect.Parameters["xView"].SetValue(Camera.ViewMatrix);
            _effect.Parameters["xProjection"].SetValue(Camera.ProjectionMatrix);
            _effect.Parameters["xWorld"].SetValue(worldMatrix);
            for (int i = 0; i < _textures.Length; i++)
            {
                _effect.Parameters["xTexture" + i].SetValue(_textures[i]);
            }
            Vector3 lightDirection = new Vector3(1.0f, -1.0f, -1.0f);
            lightDirection.Normalize();
            _effect.Parameters["xLightDirection"].SetValue(lightDirection);
            _effect.Parameters["xAmbient"].SetValue(0.5f);
            _effect.Parameters["xEnableLighting"].SetValue(true);

            foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                _device.Indices = _terrainIndexBuffer;
                _device.SetVertexBuffer(_terrainVertexBuffer);
                _device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _terrainVertexBuffer.VertexCount, 0, _terrainIndexBuffer.IndexCount / 3);
            }
        }

        public void Update(GameTime gameTime)
        {
            
        }
    }
}
